import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import {
  LatLngPoint,
  LatLngRect,
  LocationPoint,
  DetectionPoint
} from '../types/earth';
import {
  rectToPolygon,
  getCorrectLngRange,
  rectIntersects,
  pointIntersectsRect,
  getEarthScreenDiameterPrecise,
  getVisibleArcBySampling,
  getMouseRaycastForModel,
  latLngToModelSurface
} from '../utils/earthUtils';

export interface GlbRendererOptions {
  modelUrl: string;
  radius: number;
  locationPoints?: LocationPoint[];
  detectionPoints?: DetectionPoint[];
  rects?: LatLngRect[];
  onHover?: (coord: LatLngPoint | null) => void;
  onClick?: (coord: LatLngPoint | null) => void;
  onLocationHover?: (location: LocationPoint | null) => void;
  onLocationClick?: (location: LocationPoint | null) => void;
  onDetectionPointHover?: (detectionPoint: DetectionPoint | null) => void;
  onDetectionPointClick?: (detectionPoint: DetectionPoint | null) => void;
  onSelectionChange?: (selectionRect: LatLngRect | null) => void;
  onSelectionEnd?: (selectionRect: LatLngRect | null, intersectingRects: LatLngRect[], intersectingDetectionPoints: DetectionPoint[]) => void;
  simulatedMetaKey?: boolean;
}

export class GlbRenderer {
  private container: HTMLDivElement;
  private scene: THREE.Scene | null = null;
  private camera: THREE.PerspectiveCamera | null = null;
  private renderer: THREE.WebGLRenderer | null = null;
  private model: THREE.Group | null = null;
  private markers: THREE.Group | null = null;
  private raycaster: THREE.Raycaster = new THREE.Raycaster();
  private mouse: THREE.Vector2 = new THREE.Vector2();
  private modelRadius: number = 1;

  // rects & labels
  private rectLines: THREE.Line[] = [];
  private rectLabels: THREE.Sprite[] = [];

  private isDragging = false;
  private hasDragged = false;
  private lastMouse = { x: 0, y: 0 };
  private lastRotation = { x: 0, y: 0 };
  private dragStart = { x: 0, y: 0 };
  private inertia = { vx: 0, vy: 0 };
  private lastMove = { x: 0, y: 0, t: 0 };
  private animationId = 0;
  private DRAG_CLICK_BUFFER = 5;

  private selectionPoints: [LatLngPoint, LatLngPoint] | null = null;
  private selectionClickCount = 0;
  private selectionLine: THREE.Line | null = null;

  private hoverFocus: { lat: number; lng: number; point: THREE.Vector3 } | null = null;
  private focusMarker: THREE.Mesh | null = null;
  private yellowHoverPoint: { lat: number; lng: number; point: THREE.Vector3 } | null = null;
  private yellowMarker: THREE.Mesh | null = null;

  private options: GlbRendererOptions;

  constructor(container: HTMLDivElement, options: GlbRendererOptions) {
    this.container = container;
    this.options = { ...options };
    this.initScene();
    this.loadModel(options.modelUrl);
    this.bindEvents();
    this.animate();
  }

  public dispose() {
    this.unbindEvents();
    cancelAnimationFrame(this.animationId);
    if (this.focusMarker && this.markers) {
      this.markers.remove(this.focusMarker);
      this.focusMarker.geometry.dispose();
      if (Array.isArray(this.focusMarker.material)) {
        this.focusMarker.material.forEach((m: any) => m.dispose());
      } else {
        this.focusMarker.material.dispose();
      }
      this.focusMarker = null;
    }
    if (this.yellowMarker && this.markers) {
      this.markers.remove(this.yellowMarker);
      this.yellowMarker.geometry.dispose();
      if (Array.isArray(this.yellowMarker.material)) {
        this.yellowMarker.material.forEach((m: any) => m.dispose());
      } else {
        this.yellowMarker.material.dispose();
      }
      this.yellowMarker = null;
    }
    if (this.renderer && this.container) {
      if (this.renderer.domElement.parentNode === this.container) {
        this.container.removeChild(this.renderer.domElement);
      }
      this.renderer.dispose();
    }

    // cleanup rect lines
    if (this.model) {
      this.rectLines.forEach(line => {
        this.model!.remove(line);
        if (line.geometry) line.geometry.dispose();
        if (line.material) (line.material as any).dispose?.();
      });
      this.rectLines = [];
    }
    // cleanup rect labels
    if (this.model) {
      this.rectLabels.forEach(label => {
        this.model!.remove(label);
        if ((label.material as any)?.map) (label.material as any).map.dispose?.();
        (label.material as any).dispose?.();
      });
      this.rectLabels = [];
    }
  }

  private initScene() {
    const width = this.container.clientWidth || window.innerWidth;
    const height = this.container.clientHeight || window.innerHeight || 400;

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000011);
    this.scene = scene;

    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
    camera.position.set(0, 0, 5);
    this.camera = camera;

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    this.renderer = renderer;

    const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(5, 5, 5);
    directionalLight.castShadow = true;
    directionalLight.shadow.mapSize.width = 2048;
    directionalLight.shadow.mapSize.height = 2048;
    scene.add(directionalLight);

    const markers = new THREE.Group();
    this.markers = markers;
    scene.add(markers);

    this.container.appendChild(renderer.domElement);
  }

  private async loadModel(modelUrl: string) {
    if (!this.scene) { return; }
    const loader = new GLTFLoader();
    try {
      const gltf = await loader.loadAsync(modelUrl);
      const model = gltf.scene;
      this.model = model;

      const box = new THREE.Box3().setFromObject(model);
      const size = box.getSize(new THREE.Vector3());
      const maxDim = Math.max(size.x, size.y, size.z);
      const scale = 1;
      model.scale.setScalar(scale);
      const center = box.getCenter(new THREE.Vector3());
      model.position.sub(center.multiplyScalar(scale));
      this.modelRadius = (maxDim * scale) / 2;

      model.traverse((child: any) => {
        if (child instanceof THREE.Mesh) {
          child.castShadow = true;
          child.receiveShadow = true;
          if (child.material) {
            if (Array.isArray(child.material)) {
              child.material.forEach((m: any) => { m.transparent = true; m.opacity = 1; });
            } else {
              child.material.transparent = true;
              child.material.opacity = 1;
            }
          }
        }
      });

      this.scene.add(model);

      if (this.markers) {
        if (this.scene && this.markers.parent === this.scene) {
          this.scene.remove(this.markers);
        }
        model.add(this.markers);
      }

      if (this.camera) {
        const fov = this.camera.fov * (Math.PI / 180);
        const distance = Math.abs(maxDim * scale / Math.sin(fov / 2)) * 1.5;
        this.camera.position.set(0, 0, distance);
        this.camera.lookAt(0, 0, 0);
      }

      if (this.scene) {
        const dirLight = this.scene.children.find((obj: THREE.Object3D) => obj instanceof THREE.DirectionalLight) as THREE.DirectionalLight | undefined;
        if (dirLight) {
          const r = this.modelRadius * 2;
          dirLight.position.set(r, r, r);
        }
      }

      // 初始绘制标记
      this.createLocationMarkers();
      // 初始绘制rects
      this.renderRects();
    } catch (e) {
      // 交由外部处理错误可选
      // console.error('Failed to load GLB model:', e);
    }
  }

  private bindEvents() {
    if (!this.renderer) { return; }
    this.renderer.domElement.addEventListener('mousedown', this.handleMouseDown);
    this.renderer.domElement.addEventListener('mousemove', this.handleMouseMove);
    this.renderer.domElement.addEventListener('mouseup', this.handleMouseUp);
    this.renderer.domElement.addEventListener('wheel', this.handleWheel, { passive: false } as any);
    window.addEventListener('resize', this.handleResize);
  }

  private unbindEvents() {
    if (!this.renderer) { return; }
    this.renderer.domElement.removeEventListener('mousedown', this.handleMouseDown);
    this.renderer.domElement.removeEventListener('mousemove', this.handleMouseMove);
    this.renderer.domElement.removeEventListener('mouseup', this.handleMouseUp);
    this.renderer.domElement.removeEventListener('wheel', this.handleWheel as any);
    window.removeEventListener('resize', this.handleResize);
  }

  private animate = () => {
    this.animationId = requestAnimationFrame(this.animate);
    if (!this.isDragging && this.model) {
      const base = 0.008;
      const z = this.camera?.position.z || 3;
      let sensitivity = base * (z - 1);
      sensitivity = Math.max(sensitivity, 0.0008);
      this.model.rotation.y += this.inertia.vx * sensitivity * 16;
      this.model.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.model.rotation.x + this.inertia.vy * sensitivity * 16));
      this.inertia.vx *= 0.92;
      this.inertia.vy *= 0.92;
      if (Math.abs(this.inertia.vx) < 0.001) { this.inertia.vx = 0; }
      if (Math.abs(this.inertia.vy) < 0.001) { this.inertia.vy = 0; }
    }
    if (this.renderer && this.scene && this.camera) {
      this.renderer.render(this.scene, this.camera);
    }
  };

  private handleResize = () => {
    if (!this.container || !this.camera || !this.renderer) { return; }
    const width = this.container.clientWidth || window.innerWidth;
    const height = this.container.clientHeight || window.innerHeight || 400;
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(width, height);
  };

  private handleMouseDown = (event: MouseEvent) => {
    if (!this.model) { return; }
    this.isDragging = true;
    this.hasDragged = false;
    this.lastMouse = { x: event.clientX, y: event.clientY };
    this.dragStart = { x: event.clientX, y: event.clientY };
    this.lastRotation = { x: this.model.rotation.x, y: this.model.rotation.y };
    this.lastMove = { x: event.clientX, y: event.clientY, t: Date.now() };
    this.inertia = { vx: 0, vy: 0 };
  };

  private handleMouseMove = (event: MouseEvent) => {
    if (!this.container || !this.camera || !this.renderer) { return; }

    const rect = this.renderer.domElement.getBoundingClientRect();
    this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

    if (this.isDragging && this.model) {
      const dx = event.clientX - this.lastMouse.x;
      const dy = event.clientY - this.lastMouse.y;
      if (!this.hasDragged) {
        const totalDx = event.clientX - this.dragStart.x;
        const totalDy = event.clientY - this.dragStart.y;
        const dist = Math.sqrt(totalDx * totalDx + totalDy * totalDy);
        if (dist > this.DRAG_CLICK_BUFFER) { this.hasDragged = true; }
      }
      if (this.hasDragged) {
        const earthScreenDiameter = getEarthScreenDiameterPrecise(this.camera, this.renderer);
        const visibleArc = getVisibleArcBySampling(this.camera, this.renderer);
        const deltaTheta = (dx / earthScreenDiameter) * visibleArc;
        const deltaPhi = (dy / earthScreenDiameter) * visibleArc;
        this.model.rotation.y = this.lastRotation.y + deltaTheta;
        this.model.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.lastRotation.x + deltaPhi));
        const now = Date.now();
        const dt = now - this.lastMove.t;
        if (dt > 0) {
          this.inertia = { vx: (event.clientX - this.lastMove.x) / dt, vy: (event.clientY - this.lastMove.y) / dt };
        }
        this.lastMove = { x: event.clientX, y: event.clientY, t: now };
        return;
      }
    }

    // selection preview
    if (this.options.simulatedMetaKey && this.selectionPoints && this.selectionClickCount === 1) {
      const hit = this.getMouseLatLng(event);
      if (hit) {
        this.selectionPoints = [this.selectionPoints[0], hit];
        this.renderSelection();
        if (this.options.onSelectionChange) {
          const [start, end] = this.selectionPoints;
          const lngRange = getCorrectLngRange(start.lng, end.lng);
          const selectionRect: LatLngRect = {
            minLat: Math.min(start.lat, end.lat),
            maxLat: Math.max(start.lat, end.lat),
            minLng: lngRange.minLng,
            maxLng: lngRange.maxLng
          };
          this.options.onSelectionChange(selectionRect);
        }
      }
      return;
    }

    // hover on model
    if (this.model && this.camera && this.renderer) {
      const hit = getMouseRaycastForModel(event, this.camera, this.renderer, this.model.children[0]);
      if (hit) {
        const newCoord = { lat: hit.lat, lng: hit.lng };
        this.options.onHover?.(newCoord);
        this.hoverFocus = hit;
        this.yellowHoverPoint = hit;
        this.updateFocusMarker(this.hoverFocus);
        this.updateYellowMarker(this.yellowHoverPoint);
      } else {
        this.options.onHover?.(null);
        this.hoverFocus = null;
        this.yellowHoverPoint = null;
        this.updateFocusMarker(null);
        this.updateYellowMarker(null);
      }
    }

    // hover markers
    if (this.markers) {
      const intersects = this.raycaster.intersectObjects(this.markers.children, true);
      if (intersects.length > 0) {
        const marker: any = intersects[0].object;
        const userData = marker.userData;
        if (userData.type === 'location') { this.options.onLocationHover?.(userData.data); }
        else if (userData.type === 'detection') { this.options.onDetectionPointHover?.(userData.data); }
      } else {
        this.options.onLocationHover?.(null);
        this.options.onDetectionPointHover?.(null);
      }
    }
  };

  private handleMouseUp = (event: MouseEvent) => {
    if (!this.isDragging) { return; }
    this.isDragging = false;
    const dx = event.clientX - this.dragStart.x;
    const dy = event.clientY - this.dragStart.y;
    const dist = Math.sqrt(dx*dx + dy*dy);
    if (dist > this.DRAG_CLICK_BUFFER) { return; }
    this.handleMouseClick(event);
  };

  private handleMouseClick = (event: MouseEvent) => {
    if (!this.container || !this.camera || !this.renderer) { return; }
    const rect = this.renderer.domElement.getBoundingClientRect();
    this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
    this.raycaster.setFromCamera(this.mouse, this.camera);

    // markers click
    if (this.markers) {
      const intersects = this.raycaster.intersectObjects(this.markers.children, true);
      if (intersects.length > 0) {
        const marker: any = intersects[0].object;
        const userData = marker.userData;
        if (userData.type === 'location') { this.options.onLocationClick?.(userData.data); return; }
        else if (userData.type === 'detection') { this.options.onDetectionPointClick?.(userData.data); return; }
      }
    }

    // selection mode
    const selectionMode = (event as any).metaKey || this.options.simulatedMetaKey;
    if (selectionMode) {
      const hit = this.getMouseLatLng(event);
      if (hit) {
        if (this.selectionClickCount === 0) {
          this.selectionPoints = [hit, hit];
          this.selectionClickCount = 1;
          this.renderSelection();
        } else if (this.selectionClickCount === 1 && this.selectionPoints) {
          const finalSelectionPoints: [LatLngPoint, LatLngPoint] = [this.selectionPoints[0], hit];
          this.selectionPoints = finalSelectionPoints;
          this.selectionClickCount = 0;
          this.renderSelection();
          if (this.options.onSelectionEnd) {
            const [start, end] = finalSelectionPoints;
            const lngRange = getCorrectLngRange(start.lng, end.lng);
            const selectionRect: LatLngRect = {
              minLat: Math.min(start.lat, end.lat),
              maxLat: Math.max(start.lat, end.lat),
              minLng: lngRange.minLng,
              maxLng: lngRange.maxLng
            };
            const intersectingRects = (this.options.locationPoints || []).map(location => {
              const lngRange = getCorrectLngRange(location.left_longitude, location.right_longitude);
              return {
                minLat: Math.min(location.left_latitude, location.right_latitude),
                maxLat: Math.max(location.left_latitude, location.right_latitude),
                minLng: lngRange.minLng,
                maxLng: lngRange.maxLng,
                name: location.name,
                files: (location as any).files
              };
            }).filter(rect => rectIntersects(rect, selectionRect));
            const intersectingDetectionPoints = (this.options.detectionPoints || []).filter(point => {
              return pointIntersectsRect({ lat: point.center_latitude, lng: point.center_longitude }, selectionRect);
            });
            this.options.onSelectionEnd(selectionRect, intersectingRects, intersectingDetectionPoints);
          }
        }
        return;
      }
    }

    // model click
    if (this.model && this.camera && this.renderer) {
      const hit = getMouseRaycastForModel(event, this.camera, this.renderer, this.model.children[0]);
      if (hit) { this.options.onClick?.({ lat: hit.lat, lng: hit.lng }); }
    }
  };

  private handleWheel = (event: WheelEvent) => {
    if (!this.camera) { return; }
    event.preventDefault();
    const zoomSpeed = 0.1;
    const deltaY = event.deltaY;
    const currentDistance = this.camera.position.length();
    const newDistance = currentDistance + deltaY * zoomSpeed;
    const minDistance = 1.5;
    const maxDistance = 15;
    const clampedDistance = Math.max(minDistance, Math.min(maxDistance, newDistance));
    const direction = this.camera.position.clone().normalize();
    this.camera.position.copy(direction.multiplyScalar(clampedDistance));
  };

  private getMouseLatLng(event: MouseEvent): LatLngPoint | null {
    if (!this.container || !this.camera || !this.renderer || !this.model) { return null; }
    const hit = getMouseRaycastForModel(event, this.camera, this.renderer, this.model.children[0]);
    if (hit) { return { lat: hit.lat, lng: hit.lng }; }
    return null;
  }

  private createMarker(lat: number, lng: number, color: number, size: number) {
    if (!this.model) { return null; }
    const surfacePoint = latLngToModelSurface(lat, lng, this.model.children[0] as THREE.Mesh);
    const localPoint = this.model.worldToLocal(surfacePoint.clone());
    const geometry = new THREE.SphereGeometry(size, 8, 6);
    const material = new THREE.MeshBasicMaterial({ color });
    const marker = new THREE.Mesh(geometry, material);
    marker.position.copy(localPoint);
    return marker;
  }

  private createFocusMarker(point: THREE.Vector3) {
    if (!this.markers || !this.model) { return null; }
    const geometry = new THREE.SphereGeometry(0.05, 16, 16);
    const material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.8 });
    const marker = new THREE.Mesh(geometry, material);
    const localPoint = this.model.worldToLocal(point.clone());
    marker.position.copy(localPoint);
    return marker;
  }

  private createYellowMarker(lat: number, lng: number) {
    if (!this.markers || !this.model) { return null; }
    const surfacePoint = latLngToModelSurface(lat, lng, this.model.children[0] as THREE.Mesh);
    const localPoint = this.model.worldToLocal(surfacePoint.clone());
    const geometry = new THREE.SphereGeometry(0.08, 16, 16);
    const material = new THREE.MeshBasicMaterial({ color: 0xffff00, transparent: true, opacity: 0.5 });
    const marker = new THREE.Mesh(geometry, material);
    marker.position.copy(localPoint);
    return marker;
  }

  private updateFocusMarker(focus: { lat: number; lng: number; point: THREE.Vector3 } | null) {
    if (this.focusMarker && this.markers) {
      this.markers.remove(this.focusMarker);
      this.focusMarker.geometry.dispose();
      if (Array.isArray(this.focusMarker.material)) { this.focusMarker.material.forEach((m: any) => m.dispose()); }
      else { this.focusMarker.material.dispose(); }
      this.focusMarker = null;
    }
    if (focus && this.markers) {
      const newMarker = this.createFocusMarker(focus.point);
      if (newMarker) { this.markers.add(newMarker); this.focusMarker = newMarker; }
    }
  }

  private updateYellowMarker(hoverPoint: { lat: number; lng: number; point: THREE.Vector3 } | null) {
    if (this.yellowMarker && this.markers) {
      this.markers.remove(this.yellowMarker);
      this.yellowMarker.geometry.dispose();
      if (Array.isArray(this.yellowMarker.material)) { this.yellowMarker.material.forEach((m: any) => m.dispose()); }
      else { this.yellowMarker.material.dispose(); }
      this.yellowMarker = null;
    }
    if (hoverPoint && this.markers) {
      const newMarker = this.createYellowMarker(hoverPoint.lat, hoverPoint.lng);
      if (newMarker) { this.markers.add(newMarker); this.yellowMarker = newMarker; }
    }
  }

  private renderSelection() {
    if (!this.model) { return; }
    if (this.selectionLine) {
      this.model.remove(this.selectionLine);
      this.selectionLine.geometry.dispose();
      if (Array.isArray(this.selectionLine.material)) { this.selectionLine.material.forEach((m: any) => m.dispose()); }
      else { this.selectionLine.material.dispose(); }
      this.selectionLine = null;
    }
    if (this.selectionPoints && this.selectionPoints.length === 2) {
      const [p1, p2] = this.selectionPoints;
      const lngRange = getCorrectLngRange(p1.lng, p2.lng);
      const rect = { minLat: Math.min(p1.lat, p2.lat), maxLat: Math.max(p1.lat, p2.lat), minLng: lngRange.minLng, maxLng: lngRange.maxLng };
      const polygon = rectToPolygon(rect, 2);
      const borderPoints = polygon.points.map(pt => {
        const surfacePoint = latLngToModelSurface(pt.lat, pt.lng, this.model!.children[0] as THREE.Mesh);
        return this.model!.worldToLocal(surfacePoint.clone());
      });
      const geometry = new THREE.BufferGeometry().setFromPoints(borderPoints);
      const material = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 10, transparent: true, opacity: 1.0, depthTest: false, depthWrite: false });
      const line = new THREE.Line(geometry, material);
      this.model.add(line);
      this.selectionLine = line;
    }
  }

  private createLocationMarkers() {
    if (!this.markers || !this.model) { return; }
    this.markers.clear();
    (this.options.locationPoints || []).forEach((location) => {
      // 文本标签
      const label = this.createLocationLabel(location);
      if (label) {
        (label as any).userData = { type: 'location', data: location }; 
        this.markers!.add(label);
      } else {
        // 兜底：创建点标记
        const marker = this.createMarker(location.center_latitude, location.center_longitude, 0x00ff00, 0.1);
        if (marker) { (marker as any).userData = { type: 'location', data: location }; this.markers!.add(marker); }
      }
    });
    (this.options.detectionPoints || []).forEach((point) => {
      const marker = this.createMarker(point.center_latitude, point.center_longitude, 0xff0000, 0.1);
      if (marker) { (marker as any).userData = { type: 'detection', data: point }; this.markers!.add(marker); }
    });
  }

  private createTextTexture(text: string, options: { hasShadow?: boolean; hasStroke?: boolean; strokeColor?: string; strokeWidth?: number } = {}) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    if (!context) return null;
    canvas.width = 256;
    canvas.height = 96;
    context.font = 'bold 28px Arial';
    context.fillStyle = '#ffffff';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    if (options.hasShadow) {
      (context as any).shadowColor = 'rgba(0, 0, 0, 0.8)';
      (context as any).shadowBlur = 4;
      (context as any).shadowOffsetX = 2;
      (context as any).shadowOffsetY = 2;
    }
    if (options.hasStroke) {
      context.strokeStyle = options.strokeColor || '#000000';
      context.lineWidth = options.strokeWidth || 3;
    }
    const x = canvas.width / 2;
    const y = canvas.height / 2;
    if (options.hasStroke) { context.strokeText(text, x, y); }
    context.fillText(text, x, y);
    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = true;
    return texture;
  }

  private createLocationLabel(location: LocationPoint) {
    if (!this.model) { return null; }
    const texture = this.createTextTexture(location.name, { hasShadow: true });
    if (!texture) { return null; }
    const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false });
    const sprite = new THREE.Sprite(material);
    // 固定一个基础缩放；无需屏幕固定像素，保持简单
    sprite.scale.set(1, 0.4, 1);
    const surfacePoint = latLngToModelSurface(location.center_latitude, location.center_longitude, this.model.children[0] as THREE.Mesh);
    const localPoint = this.model.worldToLocal(surfacePoint.clone());
    sprite.position.copy(localPoint);
    return sprite;
  }

  private renderRects() {
    if (!this.model) { return; }
    // 清理旧线
    this.rectLines.forEach(line => {
      this.model!.remove(line);
      if (line.geometry) line.geometry.dispose();
      if (line.material) (line.material as any).dispose?.();
    });
    this.rectLines = [];
    // 清理旧label
    if (this.model) {
      this.rectLabels.forEach(label => {
        this.model!.remove(label);
        if ((label.material as any)?.map) (label.material as any).map.dispose?.();
        (label.material as any).dispose?.();
      });
      this.rectLabels = [];
    }

    const rects = this.options.rects || [];
    rects.forEach(rect => {
      const polygon = rectToPolygon(rect, 2);
      const borderPoints = polygon.points.map(pt => {
        const surfacePoint = latLngToModelSurface(pt.lat, pt.lng, this.model!.children[0] as THREE.Mesh);
        return this.model!.worldToLocal(surfacePoint.clone());
      });
      // 闭合
      if (borderPoints.length > 0) { borderPoints.push(borderPoints[0].clone()); }
      const geometry = new THREE.BufferGeometry().setFromPoints(borderPoints);
      const material = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 2, transparent: true, opacity: 1.0, depthTest: false, depthWrite: false });
      const line = new THREE.Line(geometry, material);
      this.model.add(line);
      this.rectLines.push(line);

      if (rect.name) {
        const centerLat = (rect.minLat + rect.maxLat) / 2;
        const centerLng = (rect.minLng + rect.maxLng) / 2;
        const textTex = this.createTextTexture(rect.name, { hasStroke: true });
        if (textTex) {
          const mat = new THREE.SpriteMaterial({ map: textTex, transparent: true, depthTest: false, depthWrite: false });
          const sprite = new THREE.Sprite(mat);
          sprite.scale.set(1, 0.4, 1);
          const surfacePoint = latLngToModelSurface(centerLat, centerLng, this.model.children[0] as THREE.Mesh);
          const localPoint = this.model.worldToLocal(surfacePoint.clone());
          sprite.position.copy(localPoint);
          this.model.add(sprite);
          this.rectLabels.push(sprite);
        }
      }
    });
  }

  // External API
  public setLocationPoints(points: LocationPoint[]) {
    this.options.locationPoints = points;
    this.createLocationMarkers();
  }

  public setDetectionPoints(points: DetectionPoint[]) {
    this.options.detectionPoints = points;
    this.createLocationMarkers();
  }

  public setRects(rects: LatLngRect[]) {
    this.options.rects = rects;
    this.renderRects();
  }

  public setExternalSelection(selectionRect: LatLngRect | null) {
    // Intentionally left minimal to avoid conflicting with user interactions
    // Could be extended to render external selection if needed
  }

  public setSimulatedMetaKey(active: boolean) {
    this.options.simulatedMetaKey = active;
  }

  public clearSelection() {
    this.selectionPoints = null;
    this.selectionClickCount = 0;
    if (this.selectionLine && this.model) {
      this.model.remove(this.selectionLine);
      this.selectionLine.geometry.dispose();
      if (Array.isArray(this.selectionLine.material)) { this.selectionLine.material.forEach((m: any) => m.dispose()); }
      else { this.selectionLine.material.dispose(); }
      this.selectionLine = null;
    }
    this.options.onSelectionChange?.(null);
    this.options.onSelectionEnd?.(null, [], []);
  }

  public zoomIn() {
    if (this.camera) {
      const distance = this.camera.position.length();
      this.camera.position.normalize().multiplyScalar(distance * 0.8);
    }
  }

  public zoomOut() {
    if (this.camera) {
      const distance = this.camera.position.length();
      this.camera.position.normalize().multiplyScalar(distance * 1.2);
    }
  }

  public resetView() {
    if (this.camera && this.model) {
      const box = new THREE.Box3().setFromObject(this.model);
      const size = box.getSize(new THREE.Vector3());
      const maxDim = Math.max(size.x, size.y, size.z);
      const fov = this.camera.fov * (Math.PI / 180);
      const distance = Math.abs(maxDim / Math.sin(fov / 2)) * 1.5;
      this.camera.position.set(distance, distance, distance);
      this.camera.lookAt(0, 0, 0);
      this.model.rotation.x = 0;
      this.model.rotation.y = 0;
      this.inertia = { vx: 0, vy: 0 };
    }
  }

  public getCameraZ(): number {
    return this.camera?.position.length() || 0;
  }

  public setCameraZ(z: number) {
    if (this.camera) {
      const direction = this.camera.position.clone().normalize();
      this.camera.position.copy(direction.multiplyScalar(z));
    }
  }
}


